/*
 * This confidential and proprietary software may be used only as
 * authorised by a licensing agreement from ARM Limited
 * (C) COPYRIGHT 2010-2011 ARM Limited
 * ALL RIGHTS RESERVED
 * The entire notice above must be reproduced on all authorised
 * copies and copies may only be made to the extent permitted
 * by a licensing agreement from ARM Limited.
 */

#include <ump/ump.h>
#include <memory.h>
#include <stdio.h>

/*
 * Example routine to exercise the user space UMP api.
 * This routine initializes the UMP api and allocates some CPU+device X memory.
 * No usage hints are given, so the driver will use the default cacheability policy.
 * With the allocation it creates a duplicate handle and plays with the reference count.
 * Then it simulates interacting with a device and contains pseudo code for the device.
 *
 * If any error is detected correct cleanup will be performed and -1 will be returned.
 * If successful then 0 will be returned.
 */

static int test_ump_user_api(void)
{
	/* This is the size we try to allocate*/
	const size_t alloc_size = 4096;

	ump_handle h = UMP_INVALID_MEMORY_HANDLE;
	ump_handle h_copy = UMP_INVALID_MEMORY_HANDLE;
	ump_handle h_clone = UMP_INVALID_MEMORY_HANDLE;

	void * mapping = NULL;

	ump_result ump_api_res;
	int result = -1;

	ump_secure_id id;

	size_t size_returned;

	ump_api_res = ump_open();
	if (UMP_OK != ump_api_res)
	{
		/* failed to open an ump session */
		/* early out */
		return -1;
	}

	h = ump_allocate_64(alloc_size, UMP_PROT_CPU_RD | UMP_PROT_CPU_WR | UMP_PROT_X_RD | UMP_PROT_X_WR);
	/* the refcount is now 1 */
	if (UMP_INVALID_MEMORY_HANDLE == h)
	{
		/* allocation failure */
		goto cleanup;
	}

	/* this is how we could share this allocation with another process */

	/* in process A: */
	id = ump_secure_id_get(h);
	/* still ref count 1 */
	/* send the id to process B */

	/* in process B: */
	/* receive the id from A */
	h_clone = ump_from_secure_id(id);
	/* the ref count of the allocation is now 2 (one from each handle to it) */
	/* do something ... */
	/* release our clone */
	ump_release(h_clone); /* safe to call even if ump_from_secure_id failed */
	h_clone = UMP_INVALID_MEMORY_HANDLE;


	/* a simple save-for-future-use logic inside the driver would just copy the handle (but add a ref manually too!) */
	/*
	 * void assign_memory_to_job(h)
	 * {
	  */
	h_copy = h;
	ump_retain(h_copy); /* manual retain needed as we just assigned the handle, now 2 */
	/*
	 * }
	 *
	 * void job_completed(void)
	 * {
	 */
	 ump_release(h_copy); /* normal handle release as if we got via an ump_allocate */
	 h_copy = UMP_INVALID_MEMORY_HANDLE;
	 /*
	 * }
	 */
	
	/* we're now back at ref count 1, and only h is a valid handle */
	/* enough handle duplication show-off, let's play with the contents instead */

	mapping = ump_map(h, 0, alloc_size);
	if (NULL == mapping)
	{
		/* mapping failure, either out of address space or some other error */
		goto cleanup;
	}

	memset(mapping, 0, alloc_size);

	/* let's pretend we're going to start some hw device on this buffer and read the result afterwards */
	ump_cpu_msync_now(h, UMP_MSYNC_CLEAN, mapping, alloc_size);
 	/*
		device cache invalidate

		memory barrier

		start device

		memory barrier

		wait for device

		memory barrier

		device cache clean

		memory barrier
	*/
	ump_cpu_msync_now(h, UMP_MSYNC_CLEAN_AND_INVALIDATE, mapping, alloc_size);

	/* we could now peek at the result produced by the hw device, which is now accessible via our mapping */

	/* unmap the buffer when we're done with it */
	ump_unmap(h, mapping, alloc_size);

	result = 0;

cleanup:
	ump_release(h);
	h = UMP_INVALID_MEMORY_HANDLE;

	ump_close();

	return result;
}

